package com.leyou.lyelasticsearch.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.item.entity.*;
import com.leyou.lyelasticsearch.client.BrandClient;
import com.leyou.lyelasticsearch.client.CategoryClient;
import com.leyou.lyelasticsearch.client.GoodsClient;
import com.leyou.lyelasticsearch.client.SpecificationClient;
import com.leyou.lyelasticsearch.entity.Goods;
import com.leyou.lyelasticsearch.entity.SearchRequest;
import com.leyou.lyelasticsearch.entity.SearchResult;
import com.leyou.lyelasticsearch.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private BrandClient brandClient;
    @Autowired
    private SpecificationClient specificationClient;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    @Autowired
    private GoodsRepository goodsRepository;
    private ObjectMapper mappers=new ObjectMapper();




    /*public SearchResult search(SearchRequest searchRequest) {
        //1.跟我们的elasticsearch关联起来
        String key = searchRequest.getKey();
        //对key判断是否传值过来
        if(StringUtils.isEmpty(key)){
            return null;
        }
        //2.有哪些条需要处理
        //1(对key进行查询
        //2.（分页
        //3.(根据我的cid3进行聚合
        //4.（根据brandId进行聚合
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //如果用户传的值是小米手机 == 分词后 小米 and 手机
        QueryBuilder myQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);

        //创建bool查询的对象
        BoolQueryBuilder boolQueryBuilder = buildBooleanQueryBuilder(myQuery,searchRequest);


        queryBuilder.withQuery(boolQueryBuilder);
//        queryBuilder.withQuery(myQuery);

        //结果过滤,优化速度
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));
        Integer page = searchRequest.getPage();
        Integer size = searchRequest.getSize();
        queryBuilder.withPageable(PageRequest.of(page-1,size));

        //现根据分类cid3进行聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("categories").field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brandId"));

        //讲条件查询进行之后，得到的分页对象
        //page只是分页相关的
        AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>) goodsRepository.search(queryBuilder.build());


    *//*PageResult<Goods> pageResult = new PageResult<>();
    pageResult.setTotal(pageInfo.getTotalElements());
    pageResult.setItems(pageInfo.getContent());
    pageResult.setTotalPage(pageInfo.getTotalPages());*//*


        //对聚合对象进行解析
        List<Map<String,Object>> categories = getCategoriesAggResult(pageInfo.getAggregation("categories"));

        List<TbBrand> brands = getBrandsAggResult(pageInfo.getAggregation("brands"));

        //分类有可能存在很多个
        //分类的id因为我们要通过分类的id去求规格参数有哪些
        //这次聚合，必须跟词条查询之后接着聚合

        List<Map<String,Object>> specs = specsAggResult((Long) categories.get(0).get("id"),myQuery);



//         new SearchResult(pageInfo.getTotalElements(), (long) pageInfo.getTotalPages(),pageInfo.getContent(),categories,brands,specs);
        return new SearchResult(pageInfo.getTotalElements(), (long) pageInfo.getTotalPages(),pageInfo.getContent(),categories,brands,specs);


    }*/

    /*private BoolQueryBuilder buildBooleanQueryBuilder(QueryBuilder myQuery, SearchRequest searchRequest) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //在book查询中，添加根据key值得匹配查询
        boolQueryBuilder.must(myQuery);

        Map<String, String> filter = searchRequest.getFilter();

        filter.entrySet().forEach(entity->{
            String key = entity.getKey();
            if(key.equals("品牌")){
                key = "brandId";
            } else if(key.equals("分类")){
                key = "cid3";
            } else {
                key = "specs."+key+".keyword";
            }
            String value = entity.getValue();

            boolQueryBuilder.filter(QueryBuilders.termQuery(key,value));
        });

        return boolQueryBuilder;


    }*/


    /**
     * 获取goods的分页信息
     * @param searchRequest
     * @return
     */
    public SearchResult  searchGoodsByPage(SearchRequest searchRequest){
        //如果没传key值直接返回null ，直接查询全部
        if(StringUtils.isBlank(searchRequest.getKey())){
            return null;
        }
        //构建搜索条件
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //对key进行全文搜搜
        //QueryBuilder all = QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND);
        //对key进行全文搜索的同时过滤规格参数
        QueryBuilder all = this.buildQueryBuilders(searchRequest);
        nativeSearchQueryBuilder.withQuery(all);
        //添加结果过滤
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle","price"}, null));
        //分页
        searchWithPageAndSort(searchRequest, nativeSearchQueryBuilder);
        //添加聚合查询信息
        String categoryAggsName = "categories";
        String brandAggsName = "brands";
        //对商品分类进行聚合
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAggsName).field("cid3"));
        //对品牌进行聚合
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAggsName).field("brandId"));
        //获取结果集
        AggregatedPage<Goods> search = (AggregatedPage<Goods>) goodsRepository.search(nativeSearchQueryBuilder.build());
        List<Map<String,Object>> categories=  this.getCategoriesAggResult(search.getAggregation(categoryAggsName));
        List<TbBrand> brands=  this.getBrandsAggResult(search.getAggregation(brandAggsName));

        //对规格参数进行聚合
        //判断分类聚合的大小，如果为1就聚合规格参数，否则不聚合
        //初始化参数聚合结果集
        List<Map<String,Object>> params=null;
        if(!CollectionUtils.isEmpty(categories) && categories.size()==1){
            params=this.specsAggResult((Long) categories.get(0).get("id"),all);

        }


        return new SearchResult(search.getTotalElements(), (long) search.getTotalPages(),search.getContent(),categories,brands,params);


    }

    /**
     * 获取过滤的规格参数信息
     * @param searchRequest
     * @return
     */
    private QueryBuilder buildQueryBuilders(SearchRequest searchRequest) {
        //构建布尔构建器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //基本查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all",searchRequest.getKey() ).operator(Operator.AND));
        //获取用户的过滤条件集合
        Map<String, String> filter = searchRequest.getFilter();
        //遍历我们要过滤的map集合
        filter.entrySet().stream().forEach(f->{
            String key = f.getKey();//获取要过滤的规格参数类型
            if(StringUtils.equals(key, "分类")){
                key = "cid3";
            }else if(StringUtils.equals(key, "品牌")){
                key = "brandId";
            }else{
                key="specs."+key+".keyword";
            }
            //执行过滤
            boolQueryBuilder.filter(QueryBuilders.termQuery(key,f.getValue()));
        });
        //返回过滤好的结果集
        return boolQueryBuilder;
    }

    /**
     * 基本分页查询
     * @param searchRequest
     * @param nativeSearchQueryBuilder
     */
    public void searchWithPageAndSort(SearchRequest searchRequest, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        //分页
        nativeSearchQueryBuilder.withPageable(PageRequest.of(searchRequest.getPage()-1, searchRequest.getSize()));
        //排序
        String sortBy = searchRequest.getSortBy();
        Boolean desc = searchRequest.getDescending();
        if(StringUtils.isNotBlank(sortBy)){
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc? SortOrder.DESC:SortOrder.ASC));
        }
    }

    /**
     * 获取聚合信息
     * @param id
     * @param all
     * @return
     */
    private List<Map<String, Object>> specsAggResult(Long id, QueryBuilder all) {
        //构建差选条件
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(all);
        //根据分类获取可搜索的聚合参数信息
        List<TbSpecParam> specParams = this.specificationClient.queryParams(null, id, true, null);
        List<Map<String,Object>> specs=new ArrayList<>();
        //添加聚合的规格参数信息
        specParams.stream().forEach(param -> {

            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword"));
        });
        //添加结果集过滤
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
        //执行聚合查询,获取聚合结果集
        /*AggregatedPage<Goods> search = (AggregatedPage<Goods>) this.goodsRepository.search(nativeSearchQueryBuilder.build());
        Map<String, Aggregation> aggregationMap1 = search.getAggregations().asMap();*/
        //key=聚合名称(规格参数名称)  ，value=聚合对象
        Map<String, Aggregation> aggregationMap = this.elasticsearchTemplate.query(nativeSearchQueryBuilder.build(), SearchResponse::getAggregations).asMap();
        //将规格参数聚合结果整理后返回
        //解析聚合结果

        aggregationMap.entrySet().stream().forEach(agg->{
            Map<String,Object> map = new HashMap<>();
            //存储聚合名称
            map.put("k", agg.getKey());
            //获取聚合
            StringTerms stringTerms= (StringTerms) agg.getValue();
            //获取桶中的name值并封装为集合
            List<String> collect = stringTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            map.put("options",collect );
            specs.add(map);
        });

        return specs;


    }

    /**
     * 查询品牌聚合
     * @param aggregation
     * @return
     */
    private List<TbBrand> getBrandsAggResult(Aggregation aggregation) {
        LongTerms longTerms = (LongTerms) aggregation;
        return longTerms.getBuckets().stream().map(bucket -> {
            return this.brandClient.queryBrandById(bucket.getKeyAsNumber().longValue());
        }).collect(Collectors.toList());

    }

    /**
     * 查询分类聚合
     * @param aggregation
     * @return
     */
    private List<Map<String, Object>> getCategoriesAggResult(Aggregation aggregation) {
        LongTerms longTerms= (LongTerms) aggregation;
        Map<String,Object> map = new HashMap();
        return longTerms.getBuckets().stream().map(bucket -> {
            List<String> names = this.categoryClient.queryCategoryNamesByCids(Arrays.asList(bucket.getKeyAsNumber().longValue()));
            map.put("id", bucket.getKeyAsNumber().longValue());
            map.put("name", names.get(0));
            return map;
        }).collect(Collectors.toList());
    }


    /**
     * 讲spu对象转为goods对象
     * @param spu
     * @return
     * @throws JsonProcessingException
     */
    public Goods buildGoods(TbSpu spu) throws JsonProcessingException {
        //构建goods对象
        Goods goods = new Goods();
        //获取分类名称
        List<String> names = categoryClient.queryCategoryNamesByCids(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        //根据品牌id获取品牌名称
        TbBrand brand = brandClient.queryBrandById(spu.getBrandId());

        //获取spu详情信息
        TbSpuDetail tbSpuDetail = goodsClient.querySpuDetailById(spu.getId());
        //获取sku集合
        List<TbSku> tbSkus = goodsClient.querySkusBySpuId(spu.getId());
        //获取搜索的参数信息
        List<TbSpecParam> specParams = specificationClient.queryParams(null, spu.getCid3(), true, null);
        //处理sku，仅封装id、价格、标题、图片，并获得价格集合
        List<Long> prices = new ArrayList<>();
        List<Map<String,Object>> skuList = new ArrayList<>();
        tbSkus.stream().forEach(sku ->{
            prices.add(sku.getPrice());
            Map<String,Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            skuMap.put("price", sku.getPrice());
            skuMap.put("images", StringUtils.isBlank(sku.getImages())?"":sku.getImages().split(",")[0]);
            skuList.add(skuMap);
        } );

        // 处理规格参数，反序列化为map集合
        //将通用的规格参数反序列化为map集合
        Map<String, Object> genericSpecs = mappers.readValue(tbSpuDetail.getGenericSpec(), new TypeReference<Map<String, Object>>() {
        });
        //将特有的规格参数反序列化为map集合
        Map<String, List<Object>> specialSpecs = mappers.readValue(tbSpuDetail.getSpecialSpec(), new TypeReference<Map<String, List<Object>>>() {
        });
        //过滤可搜索的规格模板，吧可搜索的信息保存到map中
        Map<String,Object> specMap = new HashMap<>();
        specParams.forEach(param->{
            //判断是否是搜索的规格参数
            if(param.getSearching()){
                //判断规格参数的类型是否是通用的规格参数
                if(param.getGeneric()){
                    String value = genericSpecs.get(param.getId().toString()).toString();
                    //判断是否是数字,如果是数字，应该返回一个区间
                    if(param.getNumeric()){
                        chooseSegment(value, param);
                    }
                    specMap.put(param.getName(), value);
                }else {
                    //如果是特有的对个参数，从specialSpecs中获取值
                    List<Object> value = specialSpecs.get(param.getId().toString());
                    specMap.put(param.getName(), value);
                }
            }
        });


        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        //拼接all字段，需要分类名称以及品牌名称
        goods.setAll(spu.getTitle()+" "+StringUtils.join(names," ")+" "+brand.getName());
        goods.setBrandId(spu.getBrandId());
        //获取sku的价格信息
        goods.setPrice(prices);

        goods.setCreateTime(spu.getCreateTime());
        //获取spu下的所有sku信息，并序列化为json字符串
        goods.setSkus(mappers.writeValueAsString(skuList));
        //获取spu下的所有的规格参数
        goods.setSpecs(specMap);
        goods.setSubTitle(spu.getSubTitle());

        return goods;

    }


    /**
     * 计算区间
     * @param value
     * @param param
     * @return
     */
    private String chooseSegment(String value,TbSpecParam param){
        double val = NumberUtils.toDouble(value);
        String result = "其他";
        //保存数值段
        for (String s : param.getSegments().split(",")) {
            String[] segments = s.split("-");
            //获取数值的范围
            double begin = NumberUtils.toDouble(segments[0]);
            double end = Double.MAX_VALUE;
            if(segments.length==2){
                end=NumberUtils.toDouble(segments[1]);
            }
            //判断是否正在范围内
            if(val >= begin && val< end){
                if(segments.length==1){
                    result =segments[0]+param.getUnit()+"以上";
                }else if(segments.length==0){
                    result = segments[1]+param.getUnit()+"以下";
                }else{
                    result = segments+param.getUnit();
                }
                break;
            }

        }
            return result;

    }

    /**
     * 根据分类id3获取分类的面包屑
     */
    public List<TbCategory> queryCategoriesById3(Long id3){
        TbCategory tbCategory3 = categoryClient.queryCategoryById(id3);
        TbCategory tbCategory2 = categoryClient.queryCategoryById(tbCategory3.getParentId());
        TbCategory tbCategory1 = categoryClient.queryCategoryById(tbCategory2.getParentId());
        return Arrays.asList(tbCategory1,tbCategory2,tbCategory3);
    }


    //保存商品信息
    public void save(Long id) throws JsonProcessingException {
        Goods goods = buildGoods(goodsClient.querySpuById(id));
        goodsRepository.save(goods);
    }

    //删除商品信息
    public void delete(Long id) {
        goodsRepository.deleteById(id);
    }
    //删除商品信息

}
